<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>Bezier样条曲线</title>
</head>
<body>
<style>
    #canvas{
        border: 1px solid #aaa;
        text-align: center;
    }
</style>
<canvas id="canvas" width="1024" height="768">
    当用户浏览器不支持Canvas,请更换浏览器重试！
</canvas>
<script type="text/javascript">
    var context = canvas.getContext("2d");//得到绘图的上下文环境

    function drawPoint(ctx, point) {
	    ctx.fillStyle="#0000FF";
	    let size = 6;
	    ctx.fillRect(point.x - size / 2, point.y - size / 2, size, size);
	}

	function cardinalSplineAt(p0, p1, p2, p3, tension, t)
	{
		let s = (1 - tension) / 2;
		let u = t;
		let u2 = u * u;
		let u3 = u2 * t;
		let car0 = s * (-u3 + 2 * u2 - u);
		let car1 = (2 - s) * u3 + (s - 3) * u2 + 1;
		let car2 = (s - 2) * u3 + (3 - 2 * s) * u2 + s * u;
		let car3 = s * (u3 - u2);

		let ret = {};
		ret.x = p0.x * car0 + p1.x * car1 + p2.x * car2 + p3.x * car3;
		ret.y = p0.y * car0 + p1.y * car1 + p2.y * car2 + p3.y * car3;
		return ret;
	}

	function binomialCoeffs(n, C)
	{
		for (let k = 0; k <= n; ++k)
		{
			// compute n!/k!(n-k)!
			C[k] = 1;
			for (let i = n; i >= k + 1; --i) C[k] *= i;
			for (let i = n - k; i >= 1; --i) C[k] /= i;
		}
		return C;
	}

	function computeBezPt(points, u, C)
	{
		let nCtrlPts = points.length;
		let n = nCtrlPts - 1;
		let pt = {x:0, y:0};
		for (let k = 0; k <= n; ++k)
		{
			let bez = C[k] * Math.pow(u, k) * Math.pow(1 - u, n - k);
			pt.x += points[k].x * bez;
			pt.y += points[k].y * bez;
		}
		return pt;
	}

	function renderBezier(points)
	{
		let C = [];
		for (let i = 0; i < points.length; ++i) C.push(1);
		binomialCoeffs(C.length - 1, C);

		let nBezCurvePts = 1000;
		let p = points[0];
	    context.beginPath();
		for (let i = 0; i <= nBezCurvePts; ++i)
		{
			context.moveTo(p.x, p.y);
			p = computeBezPt(points, i / nBezCurvePts, C);
		    context.lineTo(p.x, p.y);
		}
	    context.closePath();
	    context.lineWidth = 2;
	    context.strokeStyle = "red";
	    context.stroke();
		
	    context.beginPath();
		p = points[0];
		for (let i = 1; i < points.length; ++i)
		{
			context.moveTo(p.x, p.y);
			p = points[i];
		    context.lineTo(p.x, p.y);
		}
	    context.closePath();
	    context.lineWidth = 1;
	    context.strokeStyle = "blue";
	    context.stroke();
	}

	let p0 = {x: 400, y: 100};
	let p1 = {x: 200, y: 300};
	let p2 = {x: 600, y: 300};
	let p3 = {x: 400, y: 600};
	let p4 = {x: 200, y: 600};
	let points = [p0, p1, p2, p3, p4];

	function render()
	{
		renderBezier(points);
		
		for (let i = 0; i < points.length; ++i)
		{
			drawPoint(context, points[i]);
		}
	}
	render();

	let dragingPoint = null;
    canvas.addEventListener("mousedown", onMouseDown, false);
    canvas.addEventListener("mousemove", onMouseDrag, false);
    canvas.addEventListener("mouseup", onMouseUp, false);
	function onMouseDown(event) 
	{
		var colliderSize = 9 * 9;
		var x = event.pageX;
		var y = event.pageY;
		var canvas = event.target;
		var loc = getPointOnCanvas(canvas, x, y);
    	for (let i = 0; i < points.length; ++i)
    	{
    		if ((loc.x - points[i].x) *(loc.x - points[i].x) + (loc.y - points[i].y) * (loc.y - points[i].y) <= colliderSize)
    		{
    			dragingPoint = points[i];
    			break;
    		}
    	}
	}

	function onMouseUp(event)
	{
		dragingPoint = null;
	}

	function onMouseDrag(event)
	{
		if (dragingPoint == null) return;

		var x = event.pageX;
		var y = event.pageY;
		var canvas = event.target;
		var loc = getPointOnCanvas(canvas, x, y);
		dragingPoint.x = loc.x;
		dragingPoint.y = loc.y;
		clearCanvas();
		render();
	}

	function getPointOnCanvas(canvas, x, y) 
	{
	    var bbox = canvas.getBoundingClientRect();
	    return { 
	    	x:x - bbox.left * (canvas.width / bbox.width),
            y:y - bbox.top  * (canvas.height / bbox.height)
        };
	}

	function clearCanvas() 
	{
		context.clearRect(0, 0, 1024, 768);
	}

</script>
</body>
</html>